#Pyton library imports
from itertools import product
import re
#External libraries imports
from pyparsing import *
#Internal imports
from Exceptions import SemanticException, LabelsMismatchException
import FSPNamespace as ns
from FSPExpressionParser import *
from ParsingUtilities import *
from FSPSymbolParser import FSPSymbol

#TODO TESTARE QUESTO
def FSPVariableSolver(definitions):
  #namespace = next(definitions)
  namespace = definitions[0]
  definitions = definitions[1:]

  currentNamespace = [namespace]

  def anonymousNamespace(x):
    currentNamespace.append(ns.FSPNamespace(father=currentNamespace[-1]))
    return x

  def destroyNamespace(x):
    del currentNamespace[-1]
    return x

  ConstantIdentifier = FSPSymbol(uppercaseRegex, "const", namespace)

  RangeIdentifier = FSPSymbol(uppercaseRegex, "range", namespace)

  SetIdentifier = FSPSymbol(
    uppercaseRegex, "set", namespace).setParseAction(lambda x: [x.asList()])

  Expression = ExpressionParser(namespace=namespace, justCheck=True)

  ParameterIdentifier = UppercaseIdentifier

  def validateRangeLimits(s, l, t):
    beginning = 0
    end = 0
    try:
      beginning = eval(t[0])
      end = eval(t[1])
      if beginning > end: raise Exception()
    except:
      raise SemanticException(s, l, "Bad syntax for range expression.")
    return [range(beginning, end + 1)]

  """problem: what if one of the limits is an expression depending on a variable?
  If so, the rangeExpression remains literal and is seen as a set of 1 label
  (the range expression itself), so it can safely pass the preprocessing phase.
  In the variable resolution phase, these unresolved expressions must be resolved."""
  def manageRangeExpression(s, l, t):
    if re.match(variableRegex, t[0]) or re.match(variableRegex, t[1]):
      return [[t[0]+".."+t[1]]]
    return validateRangeLimits(s, l, t)[0]

  RangeExpression = (
    (Expression.copy().setParseAction(strJoin) + lit("..") +
    Expression.copy().setParseAction(strJoin)
    ).setParseAction(manageRangeExpression))

  Range = RangeIdentifier ^ RangeExpression

  ActionLabels = Forward()

  def uniquify(x):
    seen = {}
    result = []
    for item in x:
      if item in seen: continue
      seen[item] = 1
      result.append(item)
    return result

  SetElements = (ActionLabels + ZeroOrMore(lit(",") + ActionLabels)
    ).setParseAction(lambda l: [uniquify(l.asList())])

  SetExpression = (lit("{") + SetElements + lit("}"))

  Set = SetIdentifier ^ SetExpression

  ActionLabel = (Combine(
    (LowercaseIdentifier ^ (Literal("[") + Expression + Literal("]"))) +
      ZeroOrMore
        ((Literal(".") + LowercaseIdentifier)
          ^
          (Literal("[") + Expression + Literal("]")))))

  def pyListReprToFSPSetRepr(x):
    return "{" + str(x)[1:-1].replace("'", "") + "}"

  VariableDefinition = (
    Literal("{")+LowercaseIdentifier+Literal("}")+ Suppress(Literal(":")) +
    (
      SetIdentifier ^ SetExpression ^
      RangeIdentifier.copy().addParseAction(lambda x: [[str(i) for i in x[0]]]) ^
      RangeExpression.copy().addParseAction(lambda x: [[str(i) for i in x[0]]])
    )).setParseAction(
      lambda s,l,t: currentNamespace[-1].addName(s, l, t[1], t[3], "var"),
      lambda x: [[strJoin(["{", x[0], "}"])]])
    #parseAct: save variable to current namespace (anonymous for sure)

  ActionRange = (
      SetIdentifier
      ^SetExpression
      ^RangeIdentifier.copy().addParseAction(lambda t: [[str(i) for i in t[0]]])
      ^RangeExpression.copy().addParseAction(lambda t: [[str(i) for i in t[0]]])
      ^VariableDefinition)

  def expandActionLabels(components):
    components = components.asList()
    components = [[c] if type(c) == str else list(c) for c in components]
    strComponents = []
    for s in components:
      for e in s:
        if type(e) != str:
          s = [str(i) if i == e else e]
      strComponents.append(s)
    strComponents = [''.join(e) for e in product(*strComponents)]
    return strComponents

  class VariableView(dict):
    def __missing__(self, key):
      return "{" + key + "}"

  def variableExpansion(x):
    nonlocal currentNamespace
    x = x.asList() #contiene le stringhe da riempire col prodotto cartesiano dei valori delle variabili
    y = []
    n = currentNamespace[-1]
    unrollSpace = n.getView(nameFilter=".*", types=["var"], depth=0)
    #print("I namespace son così messi: ")
    #currentNamespace[-1].prettyPrint()
    #print("...e io mi becco questa vista: ", unrollSpace)
    matrix = []
    for k in unrollSpace.keys():
      array = []
      for v in unrollSpace[k]:
        array.append((k, v))
      matrix.append(array)
    for combination in product(*matrix):
      combinationDictionary = VariableView(combination)
      for string in x:
        try:
          #print("devo riempire la stringa ", string, " con ", combinationDictionary)
          string = string.format(**combinationDictionary)
          #print("ottengo la stringa ", string)
          y.append(string)
        except KeyError:
          pass
          #print("Cangrande")
    return y

  ActionLabels << (
      (ActionLabel ^ Set ^ (Literal("[") + ActionRange + Literal("]"))) +
      ZeroOrMore(
        (Literal(".") + ActionLabel) ^
        (Literal(".") + Set) ^
        (Literal("[") + ActionRange + Literal("]")) ^
        Combine(Literal("[") + Expression + Literal("]")))
    ).setParseAction(expandActionLabels)

  PrefixActions = (
    Group(ActionLabels) + ZeroOrMore(Literal("->") + Group(ActionLabels))
    ).setParseAction(expandActionLabels)

  Guard = Group(Keyword("when") + Expression.copy().addParseAction(strJoin))

  LocalProcess = Forward()

  def expandActions(x):
    returnList = []
    if x[0][0] == "when":
      #print("Expression della guard", x[0][1])
      if not re.match(variableRegex, x[0][1]):
        if eval(x[0][1]) == 0:
          return [""]
        else:
          for action in x[1]:
            returnList = returnList + [action] + ["->"] + [x[3]] + ["|"]
      else:
        for action in x[1]:
          returnList = returnList + ["when "] + [x[0][1]] + [" ", action] + ["->"] + [x[3]] + ["|"]
          #print("La mi' roba:", returnList)
    else:
        for action in x[0]:
          returnList = returnList + [action] + ["->"] + [x[2]] + ["|"]
    if returnList[-1] == "|": del returnList[-1]
    return strJoin(returnList)

  ActionPrefix = (Optional(Guard) + Empty().setParseAction(anonymousNamespace) +
                  Group(PrefixActions) + Literal("->") + LocalProcess
                  ).setParseAction(expandActions, variableExpansion, destroyNamespace)

  Indices = OneOrMore(Literal("[") + Expression + Literal("]"))

  IndexRanges = OneOrMore(
    (Literal("[") + Expression + Literal("]")).setParseAction(strJoin) ^
    (lit("[") + ActionRange + lit("]")).setParseAction(
      lambda x: ["[" + string + "]" for actRange in x for string in actRange]))

  ParameterDefinition = ParameterIdentifier + Literal("=") + Expression

  Parameters = (Literal("(") +
    ParameterDefinition + ZeroOrMore(lit(",") + ParameterDefinition) +
    Literal(")"))

  BaseLocalProcess = (
      Literal("END") ^ Literal("STOP") ^ Literal("ERROR") ^
      (UppercaseIdentifier + Optional(Indices))
    )

  Choice = (ActionPrefix + ZeroOrMore(lit("|") + ActionPrefix)).setParseAction(
    lambda x: addSeparator(x, "|"), strJoin)

  LocalProcess << (
      BaseLocalProcess ^
      #SequentialComposition ^
      #if Expression then LocalProcess ^
      #if Expression then LocalProcess else LocalProcess ^
      (Literal("(") + Choice + Literal(")"))
    ).setParseAction(strJoin)

  LocalProcessDefinition = (
      UppercaseIdentifier.copy().setParseAction(anonymousNamespace) +
      Optional(IndexRanges) + Literal("=") + LocalProcess
    ).setParseAction(
      strJoin, variableExpansion, lambda x: addSeparator(x, ","), destroyNamespace)

  ProcessBody = (
      LocalProcess + ZeroOrMore(Literal(",") + LocalProcessDefinition)
    )

  AlphabetExtension = (Literal("+") + Set).setParseAction(lambda x: [x[0], pyListReprToFSPSetRepr(x[1])])

  def relabelToString(s, l, x):
    if len(x[0]) != len(x[1]): raise LabelsMismatchException(s, l)
    string = ""
    for i in range(0, len(x[0])):
      string = string + x[0][i] + "/" + x[1][i] + ("," if i != len(x[0]) - 1 else "")
    return string

  RelabelRule =\
    (
      Group(ActionLabels) +
      Suppress(Literal("/")) +
      Group(ActionLabels)
    ).setParseAction(lambda s,l,t: relabelToString(s,l,t))
  #oppure forall IndexRanges { RelabelDefs }

  RelabelRules = RelabelRule + ZeroOrMore(Literal(",") + RelabelRule)

  Relabel = Literal("/") + Literal("{") + RelabelRules + Literal("}")

  SelectiveExposition = (Literal("@") + Set).setParseAction(lambda x: [x[0], pyListReprToFSPSetRepr(x[1])])

  SelectiveHiding = (Literal("\\") + Set).setParseAction(lambda x: [x[0], pyListReprToFSPSetRepr(x[1])])

  Hiding = SelectiveHiding ^ SelectiveExposition

  process = None

  def saveProcess(x):
    nonlocal process
    process = strJoin(x)
    #print("Il porccess:", process)
    return x

  ProcessDefinition =\
    (
      UppercaseIdentifier + Optional(Parameters) + Literal("=") +
      ProcessBody +
      Optional(AlphabetExtension) +
      Optional(Relabel) +
      Optional(Hiding) +
      Literal(".")
    ).setParseAction(saveProcess)

  """GENERATOR CODE"""
  yield namespace
  for definition in definitions:
    process = None
    ProcessDefinition.parseString(definition, parseAll=True)
    if process: yield process
